package io.wmy.supex.framework.aop;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import io.wmy.supex.common.util.IdBuilder;
import io.wmy.supex.common.util.JsonUtil;
import io.wmy.supex.common.util.StringUtil;
import io.wmy.supex.framework.annotation.PrintLog;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Aspect
@Component
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    private static LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
    private static Map<String, String[]> parameterNameCaches = new ConcurrentHashMap<>();

    @Pointcut("execution(public * io.wmy.supex.modules.*.controller..*.*(..))")
    private void controller() {
    }

    @Around("controller()")
    public Object setMDC(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        try {
            MDC.put("requestId", IdBuilder.getID() + " ");
            Method method = getMethod(point);

            Boolean printParams = null;
            Boolean printResult = null;
            Boolean printPretty = null;
            if (method.isAnnotationPresent(PrintLog.class) || method.getDeclaringClass().isAnnotationPresent(PrintLog.class)) {
                PrintLog printLog = method.getAnnotation(PrintLog.class);
                if (printLog == null) {
                    printLog = method.getDeclaringClass().getAnnotation(PrintLog.class);
                }
                printParams = printLog.params();
                printResult = printLog.result();
                printPretty = printLog.pretty();
            }
            String path = StringUtil.append(point.getSignature().getDeclaringTypeName(), ".", point.getSignature().getName());
            printParam(point, printParams, printPretty, path);
            Object result = point.proceed();
            printResult(method, printResult, printPretty, result);

            return result;
        } finally {
            logger.info("接口耗时：{}ms", System.currentTimeMillis() - startTime);
            MDC.clear();
        }
    }

    private void printParam(ProceedingJoinPoint point, Boolean printParams, Boolean printPretty, String path) {
        if (null == printParams) {
            return;
        }
        logger.info("目标方法：{}",path);
        if (!printParams) {
            return;
        }
        try {
            Map<String, Object> params = getParam(point);
            logger.info("输入参数：{}", JsonUtil.objectToJson(params, printPretty));
        } catch (Exception e) {
            logger.error("获取参数错误，错误信息：{}", e.getMessage());
        }
    }

    private Map getParam(ProceedingJoinPoint point) throws NoSuchMethodException {
        String methodLongName = point.getSignature().toLongString();
        String[] parameterNames = parameterNameCaches.get(methodLongName);
        if (parameterNames == null) {
            Method method = getMethod(point);
            parameterNames = parameterNameDiscoverer.getParameterNames(method);
            parameterNameCaches.put(methodLongName, parameterNames);
        }
        Object[] args = point.getArgs();
        Map<String, Object> params = new HashMap<>();
        if (args.length == parameterNames.length) {
            for (int i = 0, len = args.length; i < len; i++) {
                params.put(parameterNames[i], args[i]);
            }
        }
        return params;
    }

    private void printResult(Method method, Boolean printResult, Boolean printPretty, Object result) {
        if (null == printResult || !printResult || method.getReturnType() == Void.class) {
            return;
        }
        logger.info("返回结果：{}", JsonUtil.objectToJson(result, printPretty));
    }

    private Method getMethod(ProceedingJoinPoint point) {
        return ((MethodSignature) point.getSignature()).getMethod();
    }

}